#define UNICODE
#define _UNICODE

#include "N1Lib.h"  //MUST be before ifdef WINDOWS

#ifdef WINDOWS

#include <windows.h>
#include <imagehlp.h>  //STACKFRAME 
#include <Psapi.h> //MODULEINFO
#include <wtypes.h>
#include "N1DebugWin32.h"


static LPTOP_LEVEL_EXCEPTION_FILTER gPrevExceptionFilter = NULL;

#define EXCEPTION_BACKTRACE_MAX_SIZE 64

static N0IntWithPointerSize gN1ExceptionStack[EXCEPTION_BACKTRACE_MAX_SIZE];

void N1LogBacktrace(FILE* file, N0IntWithPointerSize *stack, unsigned int count)
{
    wchar_t buf[MAX_PATH+64];
    char    func[64];
    uint32  offs;
    N0IntWithPointerSize *pos = stack;
    HANDLE hProcess = GetCurrentProcess();
    unsigned int current_stack_element = 0;

    fprintf(file, "*** Backtrace\n");
    while (*pos != 0 || (current_stack_element==0 && count>1))
    {
        unsigned int len;
        MODULEINFO mi;
        IMAGEHLP_LINE line;

        current_stack_element++;

        line.SizeOfStruct = sizeof(IMAGEHLP_LINE);

        len = N1GetModuleFileNameByAddr( (void*)(*pos), buf, MAX_PATH );

        if(N1GetFunctionNameByAddr(*pos, func, sizeof(func), 
                                   &offs))
        {
            fprintf(file, "Function %s:%u\n", func, offs);
        }


        if (len == 0)
        {
            wsprintf( buf, L"???: 0x%p\n", *pos );
            fwprintf(file, buf);
        }
        else
        {
            HMODULE hm = GetModuleHandle(buf);
            IMAGEHLP_SYMBOL is;
            is.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);
            if (hm != NULL && GetModuleInformation(hProcess, hm, &mi, sizeof(MODULEINFO)) == TRUE)
            {   
                wsprintf( buf+len, L" (base=0x%p): 0x%p\n", mi.lpBaseOfDll , (void *)(*pos));
            }
            else
                wsprintf( buf+len, L": 0x%p Error: %u\n", (void *)(*pos), GetLastError());

            fwprintf(file, buf);
        }
        pos++;

        if (current_stack_element>=count) break;
    }
    fflush(file);
}

void N1GetStack(N0IntWithPointerSize* data, IN OUT unsigned int *count, HANDLE hProcess, HANDLE hThread, PCONTEXT pContext )
{
    STACKFRAME stackFrame;
    BOOL rv;
    unsigned int cnt;

    memset( &stackFrame, 0, sizeof(STACKFRAME) );
    stackFrame.AddrPC.Offset = pContext->Eip;
    stackFrame.AddrPC.Mode = AddrModeFlat;
    stackFrame.AddrStack.Offset = pContext->Esp;
    stackFrame.AddrStack.Mode = AddrModeFlat;
    stackFrame.AddrFrame.Offset = pContext->Ebp;
    stackFrame.AddrFrame.Mode = AddrModeFlat;

    cnt = 0;

    memset(data, 0, sizeof(N0IntWithPointerSize) * *count);

    while (cnt < *count )
    {
        rv = StackWalk( IMAGE_FILE_MACHINE_I386, hProcess, hThread,
            &stackFrame, pContext, NULL, SymFunctionTableAccess,
            SymGetModuleBase, NULL );

        if (rv == FALSE)
        {
            break;
        }

        if (stackFrame.AddrFrame.Offset == 0)
            break;

        data[cnt] = (N0IntWithPointerSize)stackFrame.AddrPC.Offset;
        cnt++;
    }

    *count = (cnt);
}

void GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo )
{
    EXCEPTION_RECORD *pExceptionRecord;
    SYSTEMTIME systemTime;
    CONTEXT *pContext;
    FILE *logFile;
    N0IntWithPointerSize *stack;
    unsigned int count;
    wchar_t buf[MAX_PATH];
    wchar_t *ModuleName;
    HANDLE hProcess;
    int isTestSystem;

    isTestSystem = 1; //GenRepIsTestSystem();
   
    hProcess = GetCurrentProcess();


    logFile = stderr;

    /********************************************************************/

    if (GetModuleFileNameW(NULL, buf, sizeof(buf)/sizeof(wchar_t)) > 0)
    {
        ModuleName = buf + wcslen(buf);

        while (ModuleName > buf)
        { 
            if ((*ModuleName == L'\\') || (*ModuleName == L'/'))
            {
                ModuleName++;
                break;
            }
            ModuleName--;
        }
    }
    else
    {
        ModuleName = L"Unknown app";
    }

    GetLocalTime( &systemTime );
    
    fprintf( logFile, "%d-%02d-%02d %02d:%02d:%02d %S[%lu] ",
        systemTime.wYear, systemTime.wMonth, systemTime.wDay,
        systemTime.wHour, systemTime.wMinute, systemTime.wSecond, ModuleName, GetCurrentProcessId());

    if (isTestSystem)
        fprintf(logFile, "System exception handled: ");
    
    pExceptionRecord = pExceptionInfo->ExceptionRecord;

    switch (pExceptionRecord->ExceptionCode)
    {
        case EXCEPTION_ACCESS_VIOLATION:
            fprintf( logFile, "Access Violation" );
        break;
	
        case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
            fprintf( logFile, "an Array Bound Exceeded" );
        break;
	
        case EXCEPTION_BREAKPOINT:
            fprintf( logFile, "a Breakpoint" );
        break;
	
        case EXCEPTION_DATATYPE_MISALIGNMENT:
            fprintf( logFile, "a Datatype Misalignment" );
        break;
	
        case EXCEPTION_FLT_DENORMAL_OPERAND:
            fprintf( logFile, "a Float Denormal Operand" );
        break;
	
        case EXCEPTION_FLT_DIVIDE_BY_ZERO:
            fprintf( logFile, "a Float Divide By Zero" );
        break;
	
        case EXCEPTION_FLT_INEXACT_RESULT:
            fprintf( logFile, "a Float Inexact Result" );
        break;
    
        case EXCEPTION_FLT_INVALID_OPERATION:
            fprintf( logFile, "a Float Invalid Operation" );
        break;
    
        case EXCEPTION_FLT_OVERFLOW:
            fprintf( logFile, "a Float Overflow" );
        break;
    
        case EXCEPTION_FLT_STACK_CHECK:
            fprintf( logFile, "a Float Stack Check" );
        break;
    
        case EXCEPTION_FLT_UNDERFLOW:
            fprintf( logFile, "a Float Underflow" );
        break;
    
        case EXCEPTION_GUARD_PAGE:
            fprintf( logFile, "a Guard Page" );
        break;

        case EXCEPTION_ILLEGAL_INSTRUCTION:
            fprintf( logFile, "an Illegal Instruction" );
        break;
    
        case EXCEPTION_IN_PAGE_ERROR:
            fprintf( logFile, "an In Page Error" );
        break;
    
        case EXCEPTION_INT_DIVIDE_BY_ZERO:
            fprintf( logFile, "an Integer Divide By Zero" );
        break;
    
        case EXCEPTION_INT_OVERFLOW:
            fprintf( logFile, "an Integer Overflow" );
        break;
    
        case EXCEPTION_INVALID_DISPOSITION:
            fprintf( logFile, "an Invalid Disposition" );
        break;
    
        case EXCEPTION_INVALID_HANDLE:
            fprintf( logFile, "an Invalid Handle" );
        break;

        case EXCEPTION_NONCONTINUABLE_EXCEPTION:
            fprintf( logFile, "a Noncontinuable Exception" );
        break;

        case EXCEPTION_PRIV_INSTRUCTION:
            fprintf( logFile, "a Privileged Instruction" );
        break;
    
        case EXCEPTION_SINGLE_STEP:
            fprintf( logFile, "a Single Step" );
        break;
    
        case EXCEPTION_STACK_OVERFLOW:
            fprintf( logFile, "a Stack Overflow" );
        break;

        case DBG_CONTROL_C:
            fprintf( logFile, "a Control+C" );
        break;
    
        case DBG_CONTROL_BREAK:
            fprintf( logFile, "a Control+Break" );
        break;
    
        case DBG_TERMINATE_THREAD:
            fprintf( logFile, "a Terminate Thread" );
        break;
    
        case DBG_TERMINATE_PROCESS:
            fprintf( logFile, "a Terminate Process" );
        break;
    
        case RPC_S_UNKNOWN_IF:
            fprintf( logFile, "an Unknown Interface" );
        break;
    
        case RPC_S_SERVER_UNAVAILABLE:
            fprintf( logFile, "a Server Unavailable" );
        break;
    
        case 0xE0000001:
        	fprintf( logFile, "Pabort");
        	if (pExceptionRecord->NumberParameters >= 1)
        		fprintf( logFile, ": %s", (const char*)pExceptionRecord->ExceptionInformation[0] );
        break;

        default:
            fprintf( logFile, "an Unknown Exception (0x%lX)", pExceptionRecord->ExceptionCode );
        break;
    }

    fprintf( logFile, " at 0x%08lx", (DWORD) pExceptionRecord->ExceptionAddress );

    if ((pExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) &&
        (pExceptionRecord->NumberParameters >= 2))
    {
        fprintf( logFile, " [%s 0x%08lx]",
            pExceptionRecord->ExceptionInformation[0] ? "writing to" : "reading from",
            pExceptionRecord->ExceptionInformation[1] );
    }

    fprintf( logFile, "\n");
    
    pContext = pExceptionInfo->ContextRecord;

    if (!isTestSystem)
    {
        fprintf( logFile, "\nRegisters:\n" );

        if (pContext->ContextFlags & CONTEXT_INTEGER)
        {
            fprintf( logFile, "eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\n",
                pContext->Eax, pContext->Ebx, pContext->Ecx, pContext->Edx,
                pContext->Esi, pContext->Edi );
        }

        if (pContext->ContextFlags & CONTEXT_CONTROL)
        {
            fprintf( logFile, "eip=%08lx esp=%08lx ebp=%08lx iopl=%1lx %s %s %s %s %s %s %s %s %s %s\n",
                pContext->Eip, pContext->Esp, pContext->Ebp,
                (pContext->EFlags >> 12) & 3,	//  IOPL level value
                pContext->EFlags & 0x00100000 ? "vip" : "   ",  //  VIP (virtual interrupt pending)
                pContext->EFlags & 0x00080000 ? "vif" : "   ",  //  VIF (virtual interrupt flag)
                pContext->EFlags & 0x00000800 ? "ov" : "nv",    //  VIF (virtual interrupt flag)
                pContext->EFlags & 0x00000400 ? "dn" : "up",    //  OF (overflow flag)
                pContext->EFlags & 0x00000200 ? "ei" : "di",    //  IF (interrupt enable flag)
                pContext->EFlags & 0x00000080 ? "ng" : "pl",    //  SF (sign flag)
                pContext->EFlags & 0x00000040 ? "zr" : "nz",    //  ZF (zero flag)
                pContext->EFlags & 0x00000010 ? "ac" : "na",    //  AF (aux carry flag)
                pContext->EFlags & 0x00000004 ? "po" : "pe",    //  PF (parity flag)
                pContext->EFlags & 0x00000001 ? "cy" : "nc"	);  //  CF (carry flag)
        }

        if (pContext->ContextFlags & CONTEXT_SEGMENTS)
        {
            fprintf( logFile, "cs=%04lx  ss=%04lx  ds=%04lx  es=%04lx  fs=%04lx  gs=%04lx\n",
                pContext->SegCs, pContext->SegSs, pContext->SegDs,
                pContext->SegEs, pContext->SegFs, pContext->SegGs );
        }

        fprintf(stderr, "System exception handled.\n");
    }

    count = EXCEPTION_BACKTRACE_MAX_SIZE;
    N1GetStack(gN1ExceptionStack, &count, GetCurrentProcess(), GetCurrentThread(), pContext );

    if(stack)
    {
        N1LogBacktrace(stderr, stack, count);
    }

    fflush( logFile );
}

static LONG WINAPI ExceptionFilter( PEXCEPTION_POINTERS pExceptionInfo )
{
    static int inHandler = 0;

    if (inHandler == 0)
    {
        inHandler = 1;

        GenerateExceptionReport(pExceptionInfo);
        abort();

        inHandler = 0;
    }

    if (gPrevExceptionFilter)
        return gPrevExceptionFilter( pExceptionInfo );
    else
        return EXCEPTION_CONTINUE_SEARCH;
}

static int ExceptionFilterCtor()
{
    gPrevExceptionFilter = SetUnhandledExceptionFilter( ExceptionFilter );
    return 0;
}

static int XX = ExceptionFilterCtor();

#endif
